import type {
    MeetingRequest,
    MeetingRequestGovernmentAgency,
    MeetingRequestIssue,
    MeetingRequestStatus,
} from "../meeting-request-data";

const API_BASE_URL = (
    process.env.NEXT_PUBLIC_API_URL?.trim() ||
    "http://localhost:3001/api/v1"
).replace(/\/+$/, "");

const API_ASSET_ORIGIN = API_BASE_URL.replace(
    /\/api\/v\d+(?:\/.*)?$/,
    "",
);

function resolveAssetUrl(path?: string | null) {
    if (!path) return null;

    const cleanPath = path.trim();

    if (!cleanPath) return null;

    if (
        /^(https?:)?\/\//i.test(cleanPath) ||
        cleanPath.startsWith("data:")
    ) {
        return cleanPath;
    }

    if (cleanPath.startsWith("/")) {
        return `${API_ASSET_ORIGIN}${cleanPath}`;
    }

    return `${API_ASSET_ORIGIN}/${cleanPath}`;
}

type ApiResponse<T> = {
    success?: boolean;
    statusCode?: number;
    message?: string;
    data?: T;
};

type ApiUser = {
    id: number;
    email?: string | null;
    name?: string | null;
    position?: string | null;
    avatar?: string | null;
    // The stakeholder(s) this user belongs to (e.g. their working group /
    // ministry). Used to show "Requested By" as the stakeholder name.
    stakeholders?: { stakeholder?: ApiStakeholder | null }[] | null;
};

type ApiStakeholder = {
    id: number;
    logo?: string | null;
    name?: string | null;
    description?: string | null;
    coChair?: string | null;
};

type ApiGovernmentAgency = {
    stakeholderId: number;
    meetingRequestId: number;
    stakeholder?: ApiStakeholder | null;
};

type ApiIssueGovernmentAgency = {
    agencyOrder: number;
    stakeholder?: ApiStakeholder | null;
};

type ApiIssue = {
    id: number;
    title?: string | null;
    description?: string | null;
    recommendation?: string | null;
    attachment?: string | null;
    issueStatus?: {
        id: number;
        code: string;
        name: string;
    } | null;
    category?: {
        id: number;
        name: string;
    } | null;
    governmentAgencies?: ApiIssueGovernmentAgency[];
};

type ApiMeetingRequest = {
    id: number;
    title?: string | null;
    description?: string | null;
    meetingRequestLetter?: string | null;
    status?: string | null;
    canCreateMeeting?: boolean;
    userId?: number;
    createdAt?: string | null;
    updatedAt?: string | null;
    deletedAt?: string | null;
    user?: ApiUser | null;
    governmentAgencies?: ApiGovernmentAgency[];
    issues?: ApiIssue[];
};

function normalizeToken(value: unknown): string | null {
    if (typeof value !== "string") {
        return null;
    }

    const token = value.trim();

    if (!token) {
        return null;
    }

    if (token.startsWith("Bearer ")) {
        return token.replace(/^Bearer\s+/i, "").trim() || null;
    }

    return token;
}

function getTokenFromJson(value: string): string | null {
    try {
        const parsed = JSON.parse(value) as Record<string, unknown>;

        const possibleToken =
            parsed.accessToken ??
            parsed.access_token ??
            parsed.token ??
            parsed.authToken ??
            parsed.jwt ??
            parsed.jwtToken;

        return normalizeToken(possibleToken);
    } catch {
        return null;
    }
}

function getAccessToken(): string | null {
    if (typeof window === "undefined") {
        return null;
    }

    const tokenKeys = [
        "accessToken",
        "access_token",
        "token",
        "authToken",
        "jwt",
        "jwtToken",
        "auth",
        "auth-storage",
        "authStore",
    ];

    for (const key of tokenKeys) {
        const value = window.localStorage.getItem(key);

        if (!value) {
            continue;
        }

        const directToken = normalizeToken(value);

        if (directToken && directToken.split(".").length === 3) {
            return directToken;
        }

        const tokenFromJson = getTokenFromJson(value);

        if (tokenFromJson) {
            return tokenFromJson;
        }
    }

    return null;
}

function getApiUrl(path: string) {
    const normalizedPath = path.startsWith("/") ? path : `/${path}`;

    return `${API_BASE_URL}${normalizedPath}`;
}

function getHeaders(headers?: HeadersInit): HeadersInit {
    const token = getAccessToken();

    return {
        Accept: "application/json",
        ...(token ? { Authorization: `Bearer ${token}` } : {}),
        ...(headers ?? {}),
    };
}

async function apiFetch<T>(
    path: string,
    options: RequestInit = {},
): Promise<T> {
    const response = await fetch(getApiUrl(path), {
        ...options,
        cache: "no-store",

        credentials: "include",

        headers: getHeaders(options.headers),
    });

    const payload = (await response
        .json()
        .catch(() => null)) as ApiResponse<T> | T | null;

    const apiPayload = payload as ApiResponse<T> | null;

    if (response.status === 401) {
        throw new Error(
            "Unauthorized. Please log out and sign in again.",
        );
    }

    if (!response.ok || apiPayload?.success === false) {
        throw new Error(
            apiPayload?.message ||
            `Failed to load meeting requests. Status: ${response.status}`,
        );
    }

    if (
        payload &&
        typeof payload === "object" &&
        "data" in payload &&
        apiPayload?.data !== undefined
    ) {
        return apiPayload.data;
    }

    return payload as T;
}

function stripHtml(value?: string | null) {
    if (!value) return "-";

    return value
        .replace(/<[^>]*>/g, " ")
        .replace(/&amp;/g, "&")
        .replace(/&nbsp;/g, " ")
        .replace(/&quot;/g, '"')
        .replace(/&#39;/g, "'")
        .replace(/\s+/g, " ")
        .trim();
}

function getYear(value?: string | null) {
    if (!value) return new Date().getFullYear();

    const date = new Date(value);

    if (Number.isNaN(date.getTime())) return new Date().getFullYear();

    return date.getFullYear();
}

function getFileName(path?: string | null) {
    if (!path) return "-";

    const fileName = path.split("/").pop();

    return fileName || "-";
}

function normalizeStatus(status?: string | null): MeetingRequestStatus {
    if (status === "DRAFT" || status === "Draft" || status === "Drafted") return "Draft";
    if (status === "SUBMITTED" || status === "Submitted") return "Submitted";
    if (status === "UNDER_REVIEW" || status === "Under Review") return "Under Review";
    if (status === "SCHEDULED" || status === "Scheduled") return "Scheduled";
    if (status === "COMPLETED" || status === "Completed") return "Completed";

    return "Submitted";
}

function mapGovernmentAgencies(
    agencies?: ApiGovernmentAgency[],
): MeetingRequestGovernmentAgency[] {
    return (agencies ?? [])
        .filter((item) => Boolean(item.stakeholder))
        .map((item) => ({
            id: item.stakeholder?.id ?? item.stakeholderId,
            name: item.stakeholder?.name ?? "-",
            logo: resolveAssetUrl(item.stakeholder?.logo),
        }));
}

function getIssueAgencySlot(
    issueAgencies: ApiIssueGovernmentAgency[] | undefined,
    meetingAgencies: MeetingRequestGovernmentAgency[],
    agencyOrder: number,
    fallback: string,
) {
    const fromIssue = (issueAgencies ?? []).find(
        (item) => item.agencyOrder === agencyOrder && item.stakeholder,
    );

    const fromMeeting = meetingAgencies[agencyOrder - 1];

    return {
        name: fromIssue?.stakeholder?.name ?? fromMeeting?.name ?? fallback,
        logo:
            resolveAssetUrl(fromIssue?.stakeholder?.logo) ??
            fromMeeting?.logo ??
            null,
    };
}

function mapIssues(
    issues: ApiIssue[] | undefined,
    meetingAgencies: MeetingRequestGovernmentAgency[],
): MeetingRequestIssue[] {
    return (issues ?? []).map((issue, index) => {
        const primary = getIssueAgencySlot(
            issue.governmentAgencies,
            meetingAgencies,
            1,
            "-",
        );

        const second = getIssueAgencySlot(
            issue.governmentAgencies,
            meetingAgencies,
            2,
            "-",
        );

        const third = getIssueAgencySlot(
            issue.governmentAgencies,
            meetingAgencies,
            3,
            "-",
        );

        const fourth = getIssueAgencySlot(
            issue.governmentAgencies,
            meetingAgencies,
            4,
            "-",
        );

        const fifth = getIssueAgencySlot(
            issue.governmentAgencies,
            meetingAgencies,
            5,
            "-",
        );

        return {
            id: issue.id || index + 1,
            issue: issue.title || "-",
            title: issue.title || "-",
            category: issue.category?.name || "-",
            description: stripHtml(issue.description),
            recommendation: stripHtml(issue.recommendation),
            status: issue.issueStatus?.name || "-",

            primaryAgency: primary.name,
            primaryAgencyLogo: primary.logo,

            secondAgency: second.name,
            secondAgencyLogo: second.logo,

            thirdAgency: third.name,
            thirdAgencyLogo: third.logo,

            fourthAgency: fourth.name,
            fourthAgencyLogo: fourth.logo,

            fifthAgency: fifth.name,
            fifthAgencyLogo: fifth.logo,
        };
    });
}

function getRequesterStakeholder(user?: ApiUser | null): ApiStakeholder | null {
    // The user's first linked stakeholder (their working group / ministry).
    return (
        user?.stakeholders?.find((link) => link.stakeholder?.name)
            ?.stakeholder ?? null
    );
}

function getRequesterStakeholderName(user?: ApiUser | null): string {
    const stakeholderName = getRequesterStakeholder(user)?.name;

    // Fall back to the personal name if the user isn't linked to a stakeholder.
    return stakeholderName?.trim() || user?.name?.trim() || "-";
}

function getRequesterCoChair(user?: ApiUser | null): string {
    // The co-chair of the requester's stakeholder (working group / ministry).
    return getRequesterStakeholder(user)?.coChair?.trim() || "-";
}

function mapMeetingRequest(item: ApiMeetingRequest): MeetingRequest {
    const governmentAgencies = mapGovernmentAgencies(
        item.governmentAgencies,
    );

    const issues = mapIssues(item.issues, governmentAgencies);

    return {
        id: item.id,
        title: item.title || "-",
        description: stripHtml(item.description),
        status: normalizeStatus(item.status),
        canCreateMeeting: item.canCreateMeeting ?? false,
        issueCount: issues.length,

        documentName: getFileName(item.meetingRequestLetter),
        meetingRequestLetter: item.meetingRequestLetter || null,

        requestedDate: item.updatedAt || item.createdAt || "-",
        requestedBy: getRequesterStakeholderName(item.user),
        year: getYear(item.updatedAt || item.createdAt),

        privateSectorWG: getRequesterStakeholderName(item.user),
        submittedBy: item.user?.name || "-",
        coChairRequestedBy: getRequesterCoChair(item.user),

        governmentAgencies,
        issues,
    };
}

export async function getMeetingRequests(): Promise<MeetingRequest[]> {
    const data = await apiFetch<ApiMeetingRequest[]>("/meeting-requests");

    return data
        .map(mapMeetingRequest)
        .filter(
            (item) =>
                item.status === "Submitted" ||
                item.status === "Under Review" ||
                item.status === "Scheduled" ||
                item.status === "Completed",
        );
}

export async function getMeetingRequestById(
    id: number,
): Promise<MeetingRequest> {
    const data = await apiFetch<ApiMeetingRequest>(
        `/meeting-requests/${id}`,
    );

    return mapMeetingRequest(data);
}

export async function startMeetingRequestReview(
    id: number,
): Promise<MeetingRequest> {
    const data = await apiFetch<ApiMeetingRequest>(
        `/meeting-requests/${id}/start-review`,
        {
            method: "PATCH",
        },
    );

    return mapMeetingRequest(data);
}
