import { config as loadEnv } from "dotenv";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";

const __dirname = dirname( fileURLToPath( import.meta.url ) );
loadEnv( { path: resolve( __dirname, "../.env" ) } );

export interface ProductLiftStatus {
	id: number;
	name: string;
	color: string;
	tab_id: number;
	tab_name: string;
}

export interface ProductLiftPostSummary {
	id: string;
	database_id: number;
	title: string;
	description_short: string;
	description: string;
	votes_count: number;
	comments_count: number;
	views_count: number;
	url: string;
	created_at: string;
	updated_at: string;
	latest_status_change_at: string | null;
	status: {
		id: number;
		name: string;
		color?: string;
		tab_name?: string;
	};
	categories?: Array<{ id: number; name: string }>;
}

export interface ProductLiftPost extends ProductLiftPostSummary {
	formatted_description?: string;
	description_changelog?: string;
	clean_description_changelog?: string;
	author?: {
		id: string;
		name: string;
		email?: string;
		role?: string;
	};
}

export interface ListPostsOptions {
	limit?: number;
	skip?: number;
	statusId?: number | number[];
	tab?: string;
	orderBy?: string;
	orderDirection?: "asc" | "desc";
	titleContains?: string;
}

export interface SearchWishesOptions {
	query: string;
	statusId?: number | number[];
	tab?: string;
	limit?: number;
	maxResults?: number;
	searchIn?: "title" | "all";
}

export class ProductLiftClient {
	private readonly baseUrl: string;
	private readonly token: string;

	constructor( portalUrl?: string, apiToken?: string ) {
		const url = portalUrl ?? process.env.PRODUCTLIFT_PORTAL_URL;
		const token = apiToken ?? process.env.PRODUCTLIFT_API_TOKEN;

		if ( ! url ) {
			throw new Error(
				"PRODUCTLIFT_PORTAL_URL is missing. Set it in tools/productlift-mcp/.env"
			);
		}
		if ( ! token ) {
			throw new Error(
				"PRODUCTLIFT_API_TOKEN is missing. Set it in tools/productlift-mcp/.env"
			);
		}

		this.baseUrl = url.replace( /\/+$/, "" );
		this.token = token;
	}

	async listCategories(): Promise<Array<{ id: number; name: string; color?: string }>> {
		const response = await this.request<{
			data: Array<{ id: number; name: string; color?: string }>;
		}>( "/api/v1/categories" );
		return response.data ?? [];
	}

	async listStatuses(): Promise<ProductLiftStatus[]> {
		const response = await this.request<{ data: ProductLiftStatus[] }>(
			"/api/v1/statuses"
		);
		return response.data ?? [];
	}

	async listPosts( options: ListPostsOptions = {} ): Promise<{
		posts: ProductLiftPostSummary[];
		total: number;
		hasMore: boolean;
		skip: number;
		limit: number;
	}> {
		const params = new URLSearchParams();
		const limit = Math.min( Math.max( options.limit ?? 25, 1 ), 100 );
		const skip = Math.max( options.skip ?? 0, 0 );

		params.set( "limit", String( limit ) );
		params.set( "skip", String( skip ) );

		if ( options.tab ) {
			params.set( "tab", options.tab );
		}
		if ( options.orderBy ) {
			params.set( "order_by", options.orderBy );
		}
		if ( options.orderDirection ) {
			params.set( "order_direction", options.orderDirection );
		}
		if ( options.titleContains ) {
			params.set( "title_condition", "contains" );
			params.set( "title_value", options.titleContains );
		}
		if ( options.statusId !== undefined ) {
			const statusIds = Array.isArray( options.statusId )
				? options.statusId
				: [ options.statusId ];
			for ( const statusId of statusIds ) {
				params.append( "status", String( statusId ) );
			}
		}

		const response = await this.request<{
			data: ProductLiftPostSummary[];
			total?: number;
			hasMore?: boolean;
			skip?: number;
			limit?: number;
		}>( `/api/v1/posts?${ params.toString() }` );

		return {
			posts: response.data ?? [],
			total: response.total ?? response.data?.length ?? 0,
			hasMore: Boolean( response.hasMore ),
			skip: response.skip ?? skip,
			limit: response.limit ?? limit,
		};
	}

	async searchWishes( options: SearchWishesOptions ): Promise<{
		query: string;
		matches: ProductLiftPostSummary[];
		total: number;
		truncated: boolean;
	}> {
		const query = options.query.trim();
		if ( ! query ) {
			return { query, matches: [], total: 0, truncated: false };
		}

		const maxResults = Math.min( Math.max( options.maxResults ?? 50, 1 ), 200 );
		const searchIn = options.searchIn ?? "all";
		const needle = query.toLowerCase();

		if ( searchIn === "title" ) {
			const page = await this.listPosts( {
				limit: Math.min( maxResults, 100 ),
				statusId: options.statusId,
				tab: options.tab,
				titleContains: query,
				orderBy: "updated_at",
				orderDirection: "desc",
			} );

			return {
				query,
				matches: page.posts.slice( 0, maxResults ),
				total: page.total,
				truncated: page.total > maxResults,
			};
		}

		const matches: ProductLiftPostSummary[] = [];
		let skip = 0;
		const pageSize = 100;
		let total = 0;
		let hasMore = true;

		while ( hasMore && matches.length < maxResults ) {
			const page = await this.listPosts( {
				limit: pageSize,
				skip,
				statusId: options.statusId,
				tab: options.tab,
				orderBy: "updated_at",
				orderDirection: "desc",
			} );

			total = page.total;
			hasMore = page.hasMore;

			for ( const post of page.posts ) {
				const haystack = [
					post.title,
					post.description_short,
					post.description,
				]
					.join( " " )
					.toLowerCase();

				if ( haystack.includes( needle ) ) {
					matches.push( post );
					if ( matches.length >= maxResults ) {
						break;
					}
				}
			}

			if ( page.posts.length === 0 ) {
				break;
			}

			skip += page.posts.length;
			if ( skip >= total ) {
				break;
			}
		}

		return {
			query,
			matches,
			total: matches.length,
			truncated: hasMore || skip < total,
		};
	}

	async getPost( postId: string ): Promise<ProductLiftPost> {
		const response = await this.request<{ data: ProductLiftPost }>(
			`/api/v1/posts/${ encodeURIComponent( postId ) }`
		);
		if ( ! response.data ) {
			throw new Error( `Post ${ postId } was not found.` );
		}
		return response.data;
	}

	async createPost( options: {
		title: string;
		description?: string;
		categoryId?: number;
		statusId?: number;
		excerpt?: string;
		isInternal?: boolean;
		priority?: "low" | "medium" | "high" | "critical";
	} ): Promise<ProductLiftPost> {
		const title = options.title.trim();
		if ( title.length < 2 ) {
			throw new Error( "Title must be at least 2 characters." );
		}
		if ( title.length > 180 ) {
			throw new Error( "Title must be at most 180 characters." );
		}

		const body: Record<string, unknown> = {
			title,
		};

		if ( options.description !== undefined ) {
			body.description = options.description;
		}
		if ( options.categoryId !== undefined ) {
			body.category_id = options.categoryId;
		}
		if ( options.statusId !== undefined ) {
			body.status_id = options.statusId;
		}
		if ( options.excerpt !== undefined ) {
			body.excerpt = options.excerpt;
		}
		if ( options.isInternal !== undefined ) {
			body.is_internal = options.isInternal ? "1" : "0";
		}
		if ( options.priority !== undefined ) {
			body.priority = options.priority;
		}

		const response = await this.request<{ data: ProductLiftPost }>(
			"/api/v1/posts",
			{
				method: "POST",
				body: JSON.stringify( body ),
				headers: {
					"Content-Type": "application/json",
				},
			}
		);

		if ( ! response.data ) {
			throw new Error( "Failed to create ProductLift post." );
		}

		return response.data;
	}

	async updatePostStatus(
		postId: string,
		statusId: number
	): Promise<ProductLiftPost> {
		const before = await this.getPost( postId );
		const formData = new URLSearchParams();
		formData.set( "status", String( statusId ) );

		const response = await this.request<{ data: ProductLiftPost }>(
			`/api/v1/posts/${ encodeURIComponent( postId ) }`,
			{
				method: "PATCH",
				body: formData.toString(),
				headers: {
					"Content-Type": "application/x-www-form-urlencoded",
				},
			}
		);

		if ( ! response.data ) {
			throw new Error( `Failed to update status for post ${ postId }.` );
		}

		return {
			...response.data,
			// Preserve audit context for callers.
			status: response.data.status ?? before.status,
		};
	}

	resolveStatusId(
		statuses: ProductLiftStatus[],
		statusNameOrId: string | number
	): ProductLiftStatus {
		if ( typeof statusNameOrId === "number" ) {
			const byId = statuses.find( ( status ) => status.id === statusNameOrId );
			if ( ! byId ) {
				throw new Error( `Status ID ${ statusNameOrId } was not found.` );
			}
			return byId;
		}

		const normalized = statusNameOrId.trim().toLowerCase();
		const exact = statuses.find(
			( status ) => status.name.toLowerCase() === normalized
		);
		if ( exact ) {
			return exact;
		}

		const partial = statuses.filter( ( status ) =>
			status.name.toLowerCase().includes( normalized )
		);
		if ( partial.length === 1 ) {
			return partial[0];
		}
		if ( partial.length > 1 ) {
			const names = partial.map( ( status ) => `${ status.id }: ${ status.name }` );
			throw new Error(
				`Status "${ statusNameOrId }" is ambiguous. Matches: ${ names.join( "; " ) }`
			);
		}

		throw new Error(
			`Status "${ statusNameOrId }" was not found. Use productlift_list_statuses to see valid values.`
		);
	}

	private async request<T>(
		path: string,
		init: RequestInit = {}
	): Promise<T> {
		const url = `${ this.baseUrl }${ path }`;
		const headers = new Headers( init.headers ?? {} );
		headers.set( "Authorization", `Bearer ${ this.token }` );
		headers.set( "Accept", "application/json" );

		const response = await fetch( url, {
			...init,
			headers,
		} );

		const text = await response.text();
		let payload: unknown = null;
		if ( text ) {
			try {
				payload = JSON.parse( text );
			} catch {
				payload = text;
			}
		}

		if ( ! response.ok ) {
			const message =
				typeof payload === "object" &&
				payload !== null &&
				"message" in payload &&
				typeof ( payload as { message: unknown } ).message === "string"
					? ( payload as { message: string } ).message
					: `ProductLift API request failed (${ response.status })`;
			throw new Error( message );
		}

		return payload as T;
	}
}

export function summarizePost( post: ProductLiftPostSummary ): string {
	const categories = post.categories?.map( ( category ) => category.name ).join( ", " );
	return [
		`ID: ${ post.id } (db ${ post.database_id })`,
		`Title: ${ post.title }`,
		`Status: ${ post.status.name } (${ post.status.id })`,
		categories ? `Categories: ${ categories }` : null,
		`Votes: ${ post.votes_count } | Comments: ${ post.comments_count } | Views: ${ post.views_count }`,
		`Updated: ${ post.updated_at }`,
		post.latest_status_change_at
			? `Last status change: ${ post.latest_status_change_at }`
			: null,
		`URL: ${ post.url }`,
	].filter( Boolean ).join( "\n" );
}

export function summarizePostDetail( post: ProductLiftPost ): string {
	const summary = summarizePost( post );
	const description = post.description_short || post.description || "";
	const changelog = post.clean_description_changelog || post.description_changelog || "";
	const author = post.author?.name ? `Author: ${ post.author.name }` : null;

	return [
		summary,
		author,
		description ? `Description: ${ description }` : null,
		changelog ? `Changelog note: ${ changelog }` : null,
	].filter( Boolean ).join( "\n\n" );
}
