package kyc

import (
	"context"
	"database/sql"
	"encoding/json"
	"errors"
	"time"

	"github.com/jackc/pgx/v5"
	"github.com/jackc/pgx/v5/pgxpool"

	"github.com/niels/banking-app/backend/internal/domain"
)

type Repository struct {
	db *pgxpool.Pool
}

type UpsertProfileParams struct {
	UserID       string
	LegalName    string
	DateOfBirth  string
	Country      string
	AddressLine1 string
	City         string
	PostalCode   string
}

type AddDocumentParams struct {
	UserID         string
	DocumentType   string
	DocumentNumber string
	Country        string
}

type ProviderDecisionParams struct {
	UserID                 string
	KYCProfileID           string
	Provider               string
	ExternalVerificationID string
	ProviderEventID        string
	Decision               string
	Reason                 string
	ConfidenceScore        *int
	RawPayload             json.RawMessage
	DecidedAt              *time.Time
}

type ProviderDecision struct {
	ID                     string          `json:"id"`
	UserID                 string          `json:"user_id"`
	KYCProfileID           string          `json:"kyc_profile_id,omitempty"`
	Provider               string          `json:"provider"`
	ExternalVerificationID string          `json:"external_verification_id"`
	ProviderEventID        string          `json:"provider_event_id,omitempty"`
	Decision               string          `json:"decision"`
	Reason                 string          `json:"reason,omitempty"`
	ConfidenceScore        *int            `json:"confidence_score,omitempty"`
	RawPayload             json.RawMessage `json:"raw_payload"`
	DecidedAt              time.Time       `json:"decided_at"`
	ReceivedAt             time.Time       `json:"received_at"`
}

type EvidenceParams struct {
	UserID             string
	KYCProfileID       string
	KYCDocumentID      string
	Provider           string
	EvidenceType       string
	ExternalEvidenceID string
	EvidenceHash       string
	Status             string
	Metadata           map[string]any
}

type Evidence struct {
	ID                 string          `json:"id"`
	UserID             string          `json:"user_id"`
	KYCProfileID       string          `json:"kyc_profile_id"`
	KYCDocumentID      string          `json:"kyc_document_id,omitempty"`
	Provider           string          `json:"provider"`
	EvidenceType       string          `json:"evidence_type"`
	ExternalEvidenceID string          `json:"external_evidence_id,omitempty"`
	EvidenceHash       string          `json:"evidence_hash,omitempty"`
	Status             string          `json:"status"`
	Metadata           json.RawMessage `json:"metadata"`
	CollectedAt        time.Time       `json:"collected_at"`
	CreatedAt          time.Time       `json:"created_at"`
}

func NewRepository(db *pgxpool.Pool) *Repository {
	return &Repository{db: db}
}

func (r *Repository) UpsertProfile(ctx context.Context, params UpsertProfileParams) (domain.KYCProfile, error) {
	row := r.db.QueryRow(ctx, `
		INSERT INTO kyc_profiles (user_id, legal_name, date_of_birth, country, address_line1, city, postal_code)
		VALUES ($1, $2, $3::date, $4, $5, $6, $7)
		ON CONFLICT (user_id) DO UPDATE
		SET legal_name = EXCLUDED.legal_name,
			date_of_birth = EXCLUDED.date_of_birth,
			country = EXCLUDED.country,
			address_line1 = EXCLUDED.address_line1,
			city = EXCLUDED.city,
			postal_code = EXCLUDED.postal_code,
			status = CASE WHEN kyc_profiles.status = 'verified' THEN 'pending' ELSE kyc_profiles.status END,
			verified_at = CASE WHEN kyc_profiles.status = 'verified' THEN NULL ELSE kyc_profiles.verified_at END
		RETURNING id::text, user_id::text, legal_name, date_of_birth::text, country, address_line1, city, postal_code,
			status, provider, COALESCE(external_verification_id, ''), COALESCE(rejection_reason, ''),
			submitted_at, verified_at, created_at, updated_at
	`, params.UserID, params.LegalName, params.DateOfBirth, params.Country, params.AddressLine1, params.City, params.PostalCode)
	return scanProfile(row)
}

func (r *Repository) FindByUser(ctx context.Context, userID string) (domain.KYCProfile, error) {
	row := r.db.QueryRow(ctx, `
		SELECT id::text, user_id::text, legal_name, date_of_birth::text, country, address_line1, city, postal_code,
			status, provider, COALESCE(external_verification_id, ''), COALESCE(rejection_reason, ''),
			submitted_at, verified_at, created_at, updated_at
		FROM kyc_profiles
		WHERE user_id = $1
	`, userID)

	profile, err := scanProfile(row)
	if errors.Is(err, pgx.ErrNoRows) {
		return domain.KYCProfile{}, domain.ErrNotFound
	}
	return profile, err
}

func (r *Repository) IsUserVerified(ctx context.Context, userID string) (bool, error) {
	var verified bool
	err := r.db.QueryRow(ctx, `
		SELECT EXISTS (
			SELECT 1 FROM kyc_profiles
			WHERE user_id = $1 AND status = 'verified'
		)
	`, userID).Scan(&verified)
	return verified, err
}

func (r *Repository) AddDocument(ctx context.Context, params AddDocumentParams) (domain.KYCDocument, error) {
	profile, err := r.FindByUser(ctx, params.UserID)
	if err != nil {
		return domain.KYCDocument{}, err
	}

	row := r.db.QueryRow(ctx, `
		INSERT INTO kyc_documents (kyc_profile_id, document_type, document_number, country)
		VALUES ($1, $2, NULLIF($3, ''), $4)
		RETURNING id::text, kyc_profile_id::text, document_type, COALESCE(document_number, ''), country, status, created_at
	`, profile.ID, params.DocumentType, params.DocumentNumber, params.Country)
	return scanDocument(row)
}

func (r *Repository) ListDocuments(ctx context.Context, userID string) ([]domain.KYCDocument, error) {
	rows, err := r.db.Query(ctx, `
		SELECT d.id::text, d.kyc_profile_id::text, d.document_type, COALESCE(d.document_number, ''), d.country, d.status, d.created_at
		FROM kyc_documents d
		JOIN kyc_profiles p ON p.id = d.kyc_profile_id
		WHERE p.user_id = $1
		ORDER BY d.created_at DESC
	`, userID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	documents := []domain.KYCDocument{}
	for rows.Next() {
		document, err := scanDocument(rows)
		if err != nil {
			return nil, err
		}
		documents = append(documents, document)
	}
	return documents, rows.Err()
}

func (r *Repository) RecordProviderDecision(ctx context.Context, params ProviderDecisionParams) (ProviderDecision, error) {
	rawPayload := jsonOrEmpty(params.RawPayload)
	row := r.db.QueryRow(ctx, `
		INSERT INTO kyc_provider_decisions (
			user_id, kyc_profile_id, provider, external_verification_id, provider_event_id,
			decision, reason, confidence_score, raw_payload, decided_at
		)
		VALUES ($1, NULLIF($2, '')::uuid, $3, $4, $5, $6, $7, $8, $9::jsonb, COALESCE($10::timestamptz, now()))
		ON CONFLICT (provider, external_verification_id, provider_event_id, decision)
		DO UPDATE SET raw_payload = EXCLUDED.raw_payload, reason = EXCLUDED.reason
		RETURNING id::text, user_id::text, COALESCE(kyc_profile_id::text, ''), provider,
			external_verification_id, provider_event_id, decision, reason, confidence_score,
			raw_payload, decided_at, received_at
	`, params.UserID, params.KYCProfileID, params.Provider, params.ExternalVerificationID, params.ProviderEventID,
		params.Decision, params.Reason, params.ConfidenceScore, string(rawPayload), params.DecidedAt)
	return scanProviderDecision(row)
}

func (r *Repository) RecordEvidence(ctx context.Context, params EvidenceParams) (Evidence, error) {
	raw, err := json.Marshal(params.Metadata)
	if err != nil {
		return Evidence{}, err
	}
	if len(raw) == 0 || string(raw) == "null" {
		raw = []byte("{}")
	}
	row := r.db.QueryRow(ctx, `
		INSERT INTO kyc_evidence (
			user_id, kyc_profile_id, kyc_document_id, provider, evidence_type,
			external_evidence_id, evidence_hash, status, metadata
		)
		VALUES ($1, $2, NULLIF($3, '')::uuid, $4, $5, $6, $7, $8, $9::jsonb)
		RETURNING id::text, user_id::text, kyc_profile_id::text, COALESCE(kyc_document_id::text, ''),
			provider, evidence_type, external_evidence_id, evidence_hash, status, metadata, collected_at, created_at
	`, params.UserID, params.KYCProfileID, params.KYCDocumentID, params.Provider, params.EvidenceType,
		params.ExternalEvidenceID, params.EvidenceHash, params.Status, string(raw))
	return scanEvidence(row)
}

func (r *Repository) ListEvidence(ctx context.Context, profileID string) ([]Evidence, error) {
	rows, err := r.db.Query(ctx, `
		SELECT id::text, user_id::text, kyc_profile_id::text, COALESCE(kyc_document_id::text, ''),
			provider, evidence_type, external_evidence_id, evidence_hash, status, metadata, collected_at, created_at
		FROM kyc_evidence
		WHERE kyc_profile_id = $1
		ORDER BY created_at DESC
	`, profileID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	evidence := []Evidence{}
	for rows.Next() {
		item, err := scanEvidence(rows)
		if err != nil {
			return nil, err
		}
		evidence = append(evidence, item)
	}
	return evidence, rows.Err()
}

func (r *Repository) ListProviderDecisions(ctx context.Context, profileID string, limit int) ([]ProviderDecision, error) {
	if limit <= 0 || limit > 100 {
		limit = 50
	}
	rows, err := r.db.Query(ctx, `
		SELECT id::text, user_id::text, COALESCE(kyc_profile_id::text, ''), provider,
			external_verification_id, provider_event_id, decision, reason, confidence_score,
			raw_payload, decided_at, received_at
		FROM kyc_provider_decisions
		WHERE kyc_profile_id = $1
		ORDER BY received_at DESC
		LIMIT $2
	`, profileID, limit)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	decisions := []ProviderDecision{}
	for rows.Next() {
		decision, err := scanProviderDecision(rows)
		if err != nil {
			return nil, err
		}
		decisions = append(decisions, decision)
	}
	return decisions, rows.Err()
}

func (r *Repository) Submit(ctx context.Context, userID, provider, externalVerificationID string) (domain.KYCProfile, error) {
	row := r.db.QueryRow(ctx, `
		UPDATE kyc_profiles
		SET status = 'pending',
			provider = $2,
			external_verification_id = $3,
			rejection_reason = NULL,
			submitted_at = now()
		WHERE user_id = $1
		RETURNING id::text, user_id::text, legal_name, date_of_birth::text, country, address_line1, city, postal_code,
			status, provider, COALESCE(external_verification_id, ''), COALESCE(rejection_reason, ''),
			submitted_at, verified_at, created_at, updated_at
	`, userID, provider, externalVerificationID)

	profile, err := scanProfile(row)
	if errors.Is(err, pgx.ErrNoRows) {
		return domain.KYCProfile{}, domain.ErrNotFound
	}
	return profile, err
}

func (r *Repository) UpdateStatusByExternalID(ctx context.Context, externalVerificationID, status, rejectionReason string) (domain.KYCProfile, error) {
	row := r.db.QueryRow(ctx, `
		UPDATE kyc_profiles
		SET status = $2,
			rejection_reason = NULLIF($3, ''),
			verified_at = CASE WHEN $2 = 'verified' THEN now() ELSE verified_at END
		WHERE external_verification_id = $1
		RETURNING id::text, user_id::text, legal_name, date_of_birth::text, country, address_line1, city, postal_code,
			status, provider, COALESCE(external_verification_id, ''), COALESCE(rejection_reason, ''),
			submitted_at, verified_at, created_at, updated_at
	`, externalVerificationID, status, rejectionReason)

	profile, err := scanProfile(row)
	if errors.Is(err, pgx.ErrNoRows) {
		return domain.KYCProfile{}, domain.ErrNotFound
	}
	return profile, err
}

func (r *Repository) UpdateStatusByProfileID(ctx context.Context, profileID, status, rejectionReason string) (domain.KYCProfile, error) {
	row := r.db.QueryRow(ctx, `
		UPDATE kyc_profiles
		SET status = $2,
			rejection_reason = NULLIF($3, ''),
			verified_at = CASE WHEN $2 = 'verified' THEN now() ELSE verified_at END
		WHERE id = $1
		RETURNING id::text, user_id::text, legal_name, date_of_birth::text, country, address_line1, city, postal_code,
			status, provider, COALESCE(external_verification_id, ''), COALESCE(rejection_reason, ''),
			submitted_at, verified_at, created_at, updated_at
	`, profileID, status, rejectionReason)

	profile, err := scanProfile(row)
	if errors.Is(err, pgx.ErrNoRows) {
		return domain.KYCProfile{}, domain.ErrNotFound
	}
	return profile, err
}

func (r *Repository) ListProfiles(ctx context.Context, status string, limit int) ([]domain.KYCProfile, error) {
	if limit <= 0 || limit > 100 {
		limit = 50
	}

	rows, err := r.db.Query(ctx, `
		SELECT id::text, user_id::text, legal_name, date_of_birth::text, country, address_line1, city, postal_code,
			status, provider, COALESCE(external_verification_id, ''), COALESCE(rejection_reason, ''),
			submitted_at, verified_at, created_at, updated_at
		FROM kyc_profiles
		WHERE NULLIF($1, '') IS NULL OR status = $1
		ORDER BY updated_at DESC
		LIMIT $2
	`, status, limit)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	profiles := []domain.KYCProfile{}
	for rows.Next() {
		profile, err := scanProfile(rows)
		if err != nil {
			return nil, err
		}
		profiles = append(profiles, profile)
	}
	return profiles, rows.Err()
}

type scanner interface {
	Scan(dest ...any) error
}

func scanProfile(row scanner) (domain.KYCProfile, error) {
	var profile domain.KYCProfile
	var submittedAt sql.NullTime
	var verifiedAt sql.NullTime
	err := row.Scan(
		&profile.ID,
		&profile.UserID,
		&profile.LegalName,
		&profile.DateOfBirth,
		&profile.Country,
		&profile.AddressLine1,
		&profile.City,
		&profile.PostalCode,
		&profile.Status,
		&profile.Provider,
		&profile.ExternalVerificationID,
		&profile.RejectionReason,
		&submittedAt,
		&verifiedAt,
		&profile.CreatedAt,
		&profile.UpdatedAt,
	)
	if submittedAt.Valid {
		profile.SubmittedAt = &submittedAt.Time
	}
	if verifiedAt.Valid {
		profile.VerifiedAt = &verifiedAt.Time
	}
	return profile, err
}

func scanDocument(row scanner) (domain.KYCDocument, error) {
	var document domain.KYCDocument
	err := row.Scan(&document.ID, &document.KYCProfileID, &document.DocumentType, &document.DocumentNumber, &document.Country, &document.Status, &document.CreatedAt)
	return document, err
}

func scanProviderDecision(row scanner) (ProviderDecision, error) {
	var decision ProviderDecision
	var confidence sql.NullInt32
	err := row.Scan(
		&decision.ID,
		&decision.UserID,
		&decision.KYCProfileID,
		&decision.Provider,
		&decision.ExternalVerificationID,
		&decision.ProviderEventID,
		&decision.Decision,
		&decision.Reason,
		&confidence,
		&decision.RawPayload,
		&decision.DecidedAt,
		&decision.ReceivedAt,
	)
	if confidence.Valid {
		score := int(confidence.Int32)
		decision.ConfidenceScore = &score
	}
	return decision, err
}

func scanEvidence(row scanner) (Evidence, error) {
	var evidence Evidence
	err := row.Scan(
		&evidence.ID,
		&evidence.UserID,
		&evidence.KYCProfileID,
		&evidence.KYCDocumentID,
		&evidence.Provider,
		&evidence.EvidenceType,
		&evidence.ExternalEvidenceID,
		&evidence.EvidenceHash,
		&evidence.Status,
		&evidence.Metadata,
		&evidence.CollectedAt,
		&evidence.CreatedAt,
	)
	return evidence, err
}

func jsonOrEmpty(raw json.RawMessage) json.RawMessage {
	if len(raw) == 0 {
		return json.RawMessage(`{}`)
	}
	return raw
}
