<?php

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

class Niwa_AI_Admin_Autonomy_External_Skills {
    const SEARCH_API_BASE        = 'https://skills.sh';
    const GITHUB_API_BASE        = 'https://api.github.com';
    const GITHUB_RAW_BASE        = 'https://raw.githubusercontent.com';
    const MAX_SEARCH_LIMIT       = 8;
    const MAX_SKILL_MD_BYTES     = 262144;
    const MAX_SUPPORT_FILES      = 40;
    const MAX_SUPPORT_FILE_BYTES = 131072;

    private $repo_meta_cache = array();

    public function search( $query, $limit = 5, $owner = '' ) {
        $query = trim( (string) $query );
        if ( '' === $query ) {
            return array(
                'success' => false,
                'error'   => 'external_skill_search query is empty.',
            );
        }

        $limit = max( 1, min( self::MAX_SEARCH_LIMIT, intval( $limit ) ) );
        $args  = array(
            'q'     => $query,
            'limit' => $limit,
        );
        $owner = $this->normalize_github_owner( $owner );
        if ( '' !== $owner ) {
            $args['owner'] = $owner;
        }

        $url      = add_query_arg( $args, self::SEARCH_API_BASE . '/api/search' );
        $response = $this->get_json( $url );
        if ( empty( $response['success'] ) ) {
            return $response;
        }

        $body   = isset( $response['body'] ) && is_array( $response['body'] ) ? $response['body'] : array();
        $skills = isset( $body['skills'] ) && is_array( $body['skills'] ) ? array_slice( $body['skills'], 0, $limit ) : array();
        $out    = array();
        foreach ( $skills as $skill ) {
            if ( ! is_array( $skill ) ) {
                continue;
            }
            $source = isset( $skill['source'] ) ? $this->normalize_github_repo( (string) $skill['source'] ) : '';
            $meta   = '' !== $source ? $this->get_repo_meta( $source ) : array();
            $installs = isset( $skill['installs'] ) ? max( 0, intval( $skill['installs'] ) ) : 0;
            $trust    = $this->assess_trust( $source, $meta, $installs );

            $out[] = array(
                'id'            => isset( $skill['id'] ) ? sanitize_text_field( (string) $skill['id'] ) : '',
                'skill_id'      => isset( $skill['skillId'] ) ? sanitize_title( (string) $skill['skillId'] ) : '',
                'name'          => isset( $skill['name'] ) ? sanitize_text_field( (string) $skill['name'] ) : '',
                'source'        => $source,
                'installs'      => $installs,
                'repo'          => $meta,
                'trust_level'   => $trust['level'],
                'risk_level'    => $trust['risk'],
                'trust_reasons' => $trust['reasons'],
                'preview_args'  => array(
                    'source' => $source,
                    'skill'  => isset( $skill['skillId'] ) ? sanitize_title( (string) $skill['skillId'] ) : '',
                    'id'     => isset( $skill['id'] ) ? sanitize_text_field( (string) $skill['id'] ) : '',
                ),
            );
        }

        return array(
            'success'             => true,
            'source'              => 'skills.sh',
            'query'               => $query,
            'search_url'          => $url,
            'count'               => count( $out ),
            'results'             => $out,
            'read_only'           => true,
            'approval_required_for_import' => true,
            'hint'                => 'Use external_skill_get to preview a result. Use skill_import_from_github only after founder approval.',
        );
    }

    public function get( $args, $include_support_files = false ) {
        $args   = is_array( $args ) ? $args : array();
        $source = $this->normalize_github_repo( isset( $args['source'] ) ? (string) $args['source'] : '' );
        $id     = isset( $args['id'] ) ? trim( (string) $args['id'] ) : '';
        $skill  = isset( $args['skill'] ) ? trim( (string) $args['skill'] ) : '';

        if ( '' === $source && '' !== $id ) {
            $parsed = $this->parse_skill_id( $id );
            $source = isset( $parsed['source'] ) ? $parsed['source'] : '';
            if ( '' === $skill && ! empty( $parsed['skill'] ) ) {
                $skill = (string) $parsed['skill'];
            }
        }
        if ( '' === $source ) {
            return array(
                'success' => false,
                'error'   => 'GitHub source is required in owner/repo form.',
            );
        }

        $meta = $this->get_repo_meta( $source );
        if ( empty( $meta['success'] ) ) {
            return array(
                'success' => false,
                'error'   => isset( $meta['error'] ) ? (string) $meta['error'] : 'Cannot read GitHub repo metadata.',
                'source'  => $source,
            );
        }

        $branch = isset( $args['branch'] ) ? sanitize_file_name( (string) $args['branch'] ) : '';
        if ( '' === $branch ) {
            $branch = ! empty( $meta['default_branch'] ) ? (string) $meta['default_branch'] : 'main';
        }

        $tree = $this->get_repo_tree( $source, $branch );
        if ( empty( $tree['success'] ) ) {
            return array(
                'success' => false,
                'error'   => isset( $tree['error'] ) ? (string) $tree['error'] : 'Cannot read GitHub tree.',
                'source'  => $source,
                'branch'  => $branch,
            );
        }

        $skill_path = $this->select_skill_path( isset( $tree['tree'] ) ? $tree['tree'] : array(), $skill, $id );
        if ( '' === $skill_path ) {
            return array(
                'success'          => false,
                'error'            => 'No matching SKILL.md found in GitHub repo.',
                'source'           => $source,
                'branch'           => $branch,
                'candidate_skills' => $this->list_skill_candidates( isset( $tree['tree'] ) ? $tree['tree'] : array(), 20 ),
            );
        }

        $raw_url = self::GITHUB_RAW_BASE . '/' . rawurlencode( $this->repo_owner( $source ) ) . '/' . rawurlencode( $this->repo_name( $source ) ) . '/' . rawurlencode( $branch ) . '/' . $this->raw_path_urlencode( $skill_path );
        $content = $this->get_text( $raw_url, self::MAX_SKILL_MD_BYTES );
        if ( empty( $content['success'] ) ) {
            return array(
                'success' => false,
                'error'   => isset( $content['error'] ) ? (string) $content['error'] : 'Cannot fetch SKILL.md.',
                'source'  => $source,
                'branch'  => $branch,
                'path'    => $skill_path,
            );
        }

        $skill_md      = (string) $content['body'];
        $skill_root    = $this->dirname_posix( $skill_path );
        $support_index = $this->discover_support_files( isset( $tree['tree'] ) ? $tree['tree'] : array(), $skill_root );
        $support_files = array();
        $support_skip  = array();
        if ( $include_support_files ) {
            foreach ( array_slice( $support_index, 0, self::MAX_SUPPORT_FILES ) as $relative_path ) {
                $support_url = self::GITHUB_RAW_BASE . '/' . rawurlencode( $this->repo_owner( $source ) ) . '/' . rawurlencode( $this->repo_name( $source ) ) . '/' . rawurlencode( $branch ) . '/' . $this->raw_path_urlencode( $skill_root . '/' . $relative_path );
                $support     = $this->get_text( $support_url, self::MAX_SUPPORT_FILE_BYTES );
                if ( ! empty( $support['success'] ) ) {
                    $support_files[ $relative_path ] = (string) $support['body'];
                } else {
                    $support_skip[] = array(
                        'path'  => $relative_path,
                        'error' => isset( $support['error'] ) ? (string) $support['error'] : 'fetch_failed',
                    );
                }
            }
        }

        $trust = $this->assess_trust( $source, $meta, 0 );
        $slug  = sanitize_title( '' !== $skill ? $skill : basename( $skill_root ) );

        return array(
            'success'        => true,
            'source'         => $source,
            'branch'         => $branch,
            'repo'           => $meta,
            'skill'          => $slug,
            'skill_path'     => $skill_path,
            'skill_root'     => $skill_root,
            'raw_url'        => $raw_url,
            'github_url'     => 'https://github.com/' . $source . '/blob/' . rawurlencode( $branch ) . '/' . $this->raw_path_urlencode( $skill_path ),
            'install_package' => $source . ( '' !== $slug ? '@' . $slug : '' ),
            'content_sha256' => hash( 'sha256', $skill_md ),
            'skill_md'       => $skill_md,
            'skill_md_preview' => $this->truncate_text( $skill_md, 4000 ),
            'support_files_available' => $support_index,
            'support_files'  => $support_files,
            'support_files_skipped' => $support_skip,
            'trust_level'    => $trust['level'],
            'risk_level'     => $trust['risk'],
            'trust_reasons'  => $trust['reasons'],
            'read_only'      => ! $include_support_files,
            'approval_required_for_import' => true,
        );
    }

    public function import_from_github( $args, $skills_manager ) {
        if ( ! $skills_manager || ! method_exists( $skills_manager, 'import_skill_from_content' ) ) {
            return array(
                'success' => false,
                'error'   => 'Skills manager is not available.',
            );
        }

        $preview = $this->get( $args, true );
        if ( empty( $preview['success'] ) ) {
            return $preview;
        }

        $filename = basename( isset( $preview['skill_root'] ) ? (string) $preview['skill_root'] : 'remote-skill' ) . '-SKILL.md';
        $result   = $skills_manager->import_skill_from_content(
            isset( $preview['skill_md'] ) ? (string) $preview['skill_md'] : '',
            $filename,
            isset( $preview['support_files'] ) && is_array( $preview['support_files'] ) ? $preview['support_files'] : array()
        );

        if ( is_array( $result ) ) {
            $result['external_source'] = array(
                'source'         => isset( $preview['source'] ) ? (string) $preview['source'] : '',
                'branch'         => isset( $preview['branch'] ) ? (string) $preview['branch'] : '',
                'skill_path'     => isset( $preview['skill_path'] ) ? (string) $preview['skill_path'] : '',
                'github_url'     => isset( $preview['github_url'] ) ? (string) $preview['github_url'] : '',
                'content_sha256' => isset( $preview['content_sha256'] ) ? (string) $preview['content_sha256'] : '',
                'trust_level'    => isset( $preview['trust_level'] ) ? (string) $preview['trust_level'] : '',
                'risk_level'     => isset( $preview['risk_level'] ) ? (string) $preview['risk_level'] : '',
            );
        }

        return is_array( $result ) ? $result : array(
            'success' => false,
            'error'   => 'Skill import returned an invalid response.',
        );
    }

    private function get_json( $url ) {
        $response = $this->http_get( $url );
        if ( empty( $response['success'] ) ) {
            return $response;
        }
        $body = json_decode( (string) $response['body'], true );
        if ( ! is_array( $body ) ) {
            return array(
                'success' => false,
                'error'   => 'Remote response is not valid JSON.',
                'url'     => $url,
            );
        }
        $response['body'] = $body;
        return $response;
    }

    private function get_text( $url, $max_bytes ) {
        $response = $this->http_get( $url );
        if ( empty( $response['success'] ) ) {
            return $response;
        }
        $body = (string) $response['body'];
        if ( strlen( $body ) > intval( $max_bytes ) ) {
            return array(
                'success' => false,
                'error'   => 'Remote file exceeds size limit.',
                'url'     => $url,
            );
        }
        return array(
            'success' => true,
            'body'    => $body,
            'url'     => $url,
        );
    }

    private function http_get( $url ) {
        $url = esc_url_raw( (string) $url );
        if ( '' === $url || ! $this->is_allowed_remote_url( $url ) ) {
            return array(
                'success' => false,
                'error'   => 'Remote URL is not allowed.',
                'url'     => $url,
            );
        }
        if ( ! function_exists( 'wp_safe_remote_get' ) ) {
            return array(
                'success' => false,
                'error'   => 'WordPress HTTP API is not available.',
                'url'     => $url,
            );
        }

        $response = wp_safe_remote_get(
            $url,
            array(
                'timeout'     => 18,
                'redirection' => 3,
                'headers'     => array(
                    'Accept'     => 'application/json, text/plain, */*',
                    'User-Agent' => 'NiwaAI/' . ( defined( 'NIWA_AI_VERSION' ) ? NIWA_AI_VERSION : 'unknown' ) . ' (+WordPress)',
                ),
            )
        );
        if ( is_wp_error( $response ) ) {
            return array(
                'success' => false,
                'error'   => $response->get_error_message(),
                'url'     => $url,
            );
        }

        $code = intval( wp_remote_retrieve_response_code( $response ) );
        $body = (string) wp_remote_retrieve_body( $response );
        if ( $code < 200 || $code >= 300 ) {
            return array(
                'success' => false,
                'error'   => 'Remote HTTP ' . $code,
                'url'     => $url,
                'body_preview' => $this->truncate_text( $body, 500 ),
            );
        }

        return array(
            'success' => true,
            'body'    => $body,
            'url'     => $url,
            'status'  => $code,
        );
    }

    private function is_allowed_remote_url( $url ) {
        $parts = wp_parse_url( $url );
        if ( empty( $parts['scheme'] ) || 'https' !== strtolower( (string) $parts['scheme'] ) || empty( $parts['host'] ) ) {
            return false;
        }
        $host = strtolower( (string) $parts['host'] );
        return in_array( $host, array( 'skills.sh', 'api.github.com', 'raw.githubusercontent.com' ), true );
    }

    private function get_repo_meta( $source ) {
        $source = $this->normalize_github_repo( $source );
        if ( '' === $source ) {
            return array( 'success' => false, 'error' => 'Invalid GitHub source.' );
        }
        if ( isset( $this->repo_meta_cache[ $source ] ) ) {
            return $this->repo_meta_cache[ $source ];
        }

        $url      = self::GITHUB_API_BASE . '/repos/' . rawurlencode( $this->repo_owner( $source ) ) . '/' . rawurlencode( $this->repo_name( $source ) );
        $response = $this->get_json( $url );
        if ( empty( $response['success'] ) ) {
            $this->repo_meta_cache[ $source ] = $response;
            return $response;
        }

        $body = isset( $response['body'] ) && is_array( $response['body'] ) ? $response['body'] : array();
        $meta = array(
            'success'        => true,
            'full_name'      => isset( $body['full_name'] ) ? sanitize_text_field( (string) $body['full_name'] ) : $source,
            'html_url'       => isset( $body['html_url'] ) ? esc_url_raw( (string) $body['html_url'] ) : '',
            'description'    => isset( $body['description'] ) ? sanitize_text_field( (string) $body['description'] ) : '',
            'stars'          => isset( $body['stargazers_count'] ) ? max( 0, intval( $body['stargazers_count'] ) ) : 0,
            'forks'          => isset( $body['forks_count'] ) ? max( 0, intval( $body['forks_count'] ) ) : 0,
            'default_branch' => isset( $body['default_branch'] ) ? sanitize_file_name( (string) $body['default_branch'] ) : 'main',
            'archived'       => ! empty( $body['archived'] ),
            'private'        => ! empty( $body['private'] ),
            'license'        => isset( $body['license']['spdx_id'] ) ? sanitize_text_field( (string) $body['license']['spdx_id'] ) : '',
        );
        $this->repo_meta_cache[ $source ] = $meta;
        return $meta;
    }

    private function get_repo_tree( $source, $branch ) {
        $source = $this->normalize_github_repo( $source );
        $branch = sanitize_file_name( (string) $branch );
        $url    = self::GITHUB_API_BASE . '/repos/' . rawurlencode( $this->repo_owner( $source ) ) . '/' . rawurlencode( $this->repo_name( $source ) ) . '/git/trees/' . rawurlencode( $branch ) . '?recursive=1';
        $result = $this->get_json( $url );
        if ( empty( $result['success'] ) ) {
            return $result;
        }
        $body = isset( $result['body'] ) && is_array( $result['body'] ) ? $result['body'] : array();
        return array(
            'success' => true,
            'tree'    => isset( $body['tree'] ) && is_array( $body['tree'] ) ? $body['tree'] : array(),
            'truncated' => ! empty( $body['truncated'] ),
        );
    }

    private function select_skill_path( $tree, $skill, $id ) {
        $candidates = $this->list_skill_candidates( $tree, 500 );
        if ( empty( $candidates ) ) {
            return '';
        }

        $needle = sanitize_title( '' !== trim( (string) $skill ) ? (string) $skill : (string) $id );
        if ( '' === $needle && 1 === count( $candidates ) ) {
            return (string) $candidates[0]['path'];
        }

        $best_path  = '';
        $best_score = -1;
        foreach ( $candidates as $candidate ) {
            $path        = isset( $candidate['path'] ) ? (string) $candidate['path'] : '';
            $parent_slug = sanitize_title( basename( dirname( $path ) ) );
            $path_slug   = sanitize_title( str_replace( array( '/', '\\', 'SKILL.md' ), ' ', $path ) );
            $score       = 0;
            if ( '' !== $needle && $parent_slug === $needle ) {
                $score += 100;
            }
            if ( '' !== $needle && false !== strpos( $path_slug, $needle ) ) {
                $score += 35;
            }
            if ( preg_match( '#(^|/)skills/[^/]+/SKILL\.md$#', $path ) ) {
                $score += 12;
            }
            if ( 'SKILL.md' === $path ) {
                $score += 4;
            }
            if ( $score > $best_score ) {
                $best_score = $score;
                $best_path  = $path;
            }
        }

        return $best_score > 0 || 1 === count( $candidates ) ? $best_path : '';
    }

    private function list_skill_candidates( $tree, $limit ) {
        $out = array();
        foreach ( is_array( $tree ) ? $tree : array() as $entry ) {
            if ( ! is_array( $entry ) || ( isset( $entry['type'] ) && 'blob' !== (string) $entry['type'] ) ) {
                continue;
            }
            $path = isset( $entry['path'] ) ? trim( (string) $entry['path'] ) : '';
            if ( '' === $path || ! preg_match( '#(^|/)SKILL\.md$#', $path ) ) {
                continue;
            }
            $out[] = array(
                'path' => $path,
                'size' => isset( $entry['size'] ) ? max( 0, intval( $entry['size'] ) ) : 0,
            );
            if ( count( $out ) >= max( 1, intval( $limit ) ) ) {
                break;
            }
        }

        return $out;
    }

    private function discover_support_files( $tree, $skill_root ) {
        $skill_root = trim( (string) $skill_root, '/' );
        $prefix     = '' === $skill_root ? '' : $skill_root . '/';
        $out        = array();

        foreach ( is_array( $tree ) ? $tree : array() as $entry ) {
            if ( ! is_array( $entry ) || ( isset( $entry['type'] ) && 'blob' !== (string) $entry['type'] ) ) {
                continue;
            }
            $path = isset( $entry['path'] ) ? trim( (string) $entry['path'] ) : '';
            if ( '' === $path || ( '' !== $prefix && 0 !== strpos( $path, $prefix ) ) ) {
                continue;
            }
            $relative = '' !== $prefix ? substr( $path, strlen( $prefix ) ) : $path;
            if ( ! preg_match( '#^(references|scripts|assets)/#', $relative ) ) {
                continue;
            }
            $size = isset( $entry['size'] ) ? max( 0, intval( $entry['size'] ) ) : 0;
            if ( $size > self::MAX_SUPPORT_FILE_BYTES ) {
                continue;
            }
            $out[] = $relative;
            if ( count( $out ) >= self::MAX_SUPPORT_FILES ) {
                break;
            }
        }

        sort( $out );
        return $out;
    }

    private function assess_trust( $source, $repo_meta, $installs ) {
        $source    = $this->normalize_github_repo( $source );
        $repo_meta = is_array( $repo_meta ) ? $repo_meta : array();
        $owner     = strtolower( $this->repo_owner( $source ) );
        $stars     = isset( $repo_meta['stars'] ) ? intval( $repo_meta['stars'] ) : 0;
        $installs  = max( 0, intval( $installs ) );
        $reasons   = array();
        $level     = 'low';
        $risk      = 'medium';

        if ( in_array( $owner, array( 'vercel', 'vercel-labs', 'anthropics', 'anthropic-ai', 'microsoft', 'openai' ), true ) ) {
            $level     = 'high';
            $risk      = 'low';
            $reasons[] = 'recognized_owner';
        }
        if ( $stars >= 500 || $installs >= 1000 ) {
            $level     = 'high';
            $risk      = 'low';
            $reasons[] = $stars >= 500 ? 'high_repo_stars' : 'high_skill_installs';
        } elseif ( $stars >= 50 || $installs >= 100 ) {
            if ( 'high' !== $level ) {
                $level = 'medium';
                $risk  = 'medium';
            }
            $reasons[] = $stars >= 50 ? 'moderate_repo_stars' : 'moderate_skill_installs';
        }
        if ( ! empty( $repo_meta['archived'] ) ) {
            $risk      = 'high';
            $reasons[] = 'repo_archived';
        }
        if ( ! empty( $repo_meta['private'] ) ) {
            $risk      = 'high';
            $reasons[] = 'repo_private';
        }
        if ( empty( $reasons ) ) {
            $reasons[] = 'limited_public_reputation';
        }

        return array(
            'level'   => $level,
            'risk'    => $risk,
            'reasons' => $reasons,
        );
    }

    private function parse_skill_id( $id ) {
        $id    = trim( (string) $id );
        $parts = array_values( array_filter( explode( '/', $id ), 'strlen' ) );
        if ( count( $parts ) < 2 ) {
            return array();
        }

        return array(
            'source' => $this->normalize_github_repo( $parts[0] . '/' . $parts[1] ),
            'skill'  => isset( $parts[2] ) ? sanitize_title( implode( '-', array_slice( $parts, 2 ) ) ) : '',
        );
    }

    private function normalize_github_repo( $repo ) {
        $repo = trim( (string) $repo );
        $repo = preg_replace( '#^https://github\.com/#i', '', $repo );
        $repo = preg_replace( '#\.git$#i', '', $repo );
        $repo = trim( $repo, "/ \t\n\r\0\x0B" );
        if ( ! preg_match( '#^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$#', $repo ) ) {
            return '';
        }

        return $repo;
    }

    private function normalize_github_owner( $owner ) {
        $owner = trim( (string) $owner );
        return preg_match( '#^[A-Za-z0-9_.-]+$#', $owner ) ? $owner : '';
    }

    private function repo_owner( $repo ) {
        $parts = explode( '/', (string) $repo, 2 );
        return isset( $parts[0] ) ? $parts[0] : '';
    }

    private function repo_name( $repo ) {
        $parts = explode( '/', (string) $repo, 2 );
        return isset( $parts[1] ) ? $parts[1] : '';
    }

    private function dirname_posix( $path ) {
        $path = trim( str_replace( '\\', '/', (string) $path ), '/' );
        if ( '' === $path || false === strpos( $path, '/' ) ) {
            return '';
        }

        return trim( dirname( $path ), '.' );
    }

    private function raw_path_urlencode( $path ) {
        $parts = array_map( 'rawurlencode', array_values( array_filter( explode( '/', str_replace( '\\', '/', (string) $path ) ), 'strlen' ) ) );
        return implode( '/', $parts );
    }

    private function truncate_text( $text, $limit ) {
        $text  = trim( (string) $text );
        $limit = max( 20, intval( $limit ) );
        if ( '' === $text ) {
            return '';
        }
        if ( function_exists( 'mb_strlen' ) && mb_strlen( $text, 'UTF-8' ) <= $limit ) {
            return $text;
        }
        if ( ! function_exists( 'mb_strlen' ) && strlen( $text ) <= $limit ) {
            return $text;
        }

        return function_exists( 'mb_substr' )
            ? rtrim( mb_substr( $text, 0, $limit - 1, 'UTF-8' ) ) . '...'
            : rtrim( substr( $text, 0, $limit - 1 ) ) . '...';
    }
}
